/*************************************************
* Title：TinyToolKit
* Author：Opportunity 
* Describe：异步Socket接收网络数据，拥有粘包分包处理、心跳机制、消息分发等功能
 * 用消息列表将多线程异步回调转为单线程，使用注册委托的方法来处理消息
* CreateTime：2020-08-13 11:55:02
* Version：1.0
* Modify Recorder：
*************************************************/

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

namespace TinyToolKit.Network
{
    public enum Status
    {
        Unconnected,
        Connected
    }

    public class TcpClientHelper
    {
        private TcpClient _tcpClient;
        private IPEndPoint _ipEndPoint;
        private ProtocolBytes _protocol;
        private Message _message;
        private MsgDistribution _msgDist;
        private Status _status;

        private readonly float _heartBeatTime;
        private float _lastTickTime;
        private float _reconnectTime;
        private float _lastReconnectTime;
        private bool _initHeartBeatFlag;
        private bool _activeInterruptFlag;
        private bool _isReconnecting;
        private bool _activeSuccessFlag;

        private Action _successConnectCb; //网络连接成功委托事件
        private Action _interruptConnectCb; //网络异常断开委托事件

        private NetworkStream TcpClientStream
        {
            get
            {
                if (_tcpClient != null && _tcpClient.Connected)
                    return _tcpClient.GetStream();

                Close();

                return null;
            }
        }

        public IPEndPoint LocalEndPoint => _tcpClient.Client.LocalEndPoint as IPEndPoint;

        #region Public Func

        public TcpClientHelper()
        {
            _message = new Message();
            _protocol = new ProtocolBytes();
            _msgDist = new MsgDistribution();

            _status = Status.Unconnected;
            _heartBeatTime = 20;
            _reconnectTime = 1;
        }

        /// <summary>
        /// 开启客户端连接
        /// </summary>
        /// <param name="ip">服务端的IP地址</param>
        /// <param name="port">连接的端口号</param>
        /// <param name="successConnectCb">成功连接服务端的回调函数</param>
        /// <param name="interruptConnectCb">与服务端断开连接的回调函数</param>
        public bool Connect(string ip, int port, Action successConnectCb = null, Action interruptConnectCb = null)
        {
            try
            {
                _ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
                _tcpClient = new TcpClient();
                _tcpClient.BeginConnect(_ipEndPoint.Address, _ipEndPoint.Port, OnClientConnect, _tcpClient);
                TLogTool.Info("【TcpClientNet】客户端开启，准备连接服务端");

                _successConnectCb = successConnectCb;
                _interruptConnectCb = interruptConnectCb;
                return true;
            }
            catch (Exception ex)
            {
                TLogTool.Info("【TcpClientNet】客户端连接服务器时发生错误：" + ex);
                return false;
            }
        }

        /// <summary>
        /// 更新
        /// </summary>
        public void Update()
        {
            _msgDist.Update();

            //每隔一定时间发送心跳协议
            if (_status == Status.Connected)
            {
                if (_activeSuccessFlag)
                {
                    _successConnectCb?.Invoke();
                    _activeSuccessFlag = false;
                }

                if (_initHeartBeatFlag == false)
                {
                    SendHeartBeat();
                    _initHeartBeatFlag = true;
                }
                else
                {
                    if (Time.realtimeSinceStartup - _lastTickTime > _heartBeatTime)
                    {
                        SendHeartBeat();
                        _lastTickTime = Time.realtimeSinceStartup;
                    }
                }
            }
            else
            {
                _lastTickTime = Time.realtimeSinceStartup;

                if (_activeInterruptFlag)
                {
                    TLogTool.Error("【TcpClientNet】当前客户端与服务端网络连接异常，数据发送失败！");
                    _interruptConnectCb?.Invoke();
                    _isReconnecting = true;

                    _activeInterruptFlag = false;
                }

                if (_isReconnecting)
                {
                    if (Time.realtimeSinceStartup - _lastReconnectTime > _reconnectTime)
                    {
                        Reconnect();
                        _lastReconnectTime = Time.realtimeSinceStartup;
                    }
                }
            }
        }

        /// <summary>
        /// 向服务器端发送消息协议
        /// </summary>
        /// <param name="protocol">需要发送的消息协议</param>
        /// <returns>返回是否发送消息成功</returns>
        public bool Send(ProtocolBase protocol)
        {
            if (_status == Status.Connected && TcpClientStream != null)
            {
                byte[] temp = _message.PackMessage(protocol.Encode());

                TLogTool.Info($"【TcpClientNet】客户端向服务端发送{protocol.GetRequest()}请求下的{protocol.GetName()}协议");
                TcpClientStream.Write(temp, 0, temp.Length);
                return true;
            }

            _activeInterruptFlag = true;
            return false;
        }

        public void AddEventListener(string name, Action<ProtocolBase> cb)
        {
            _msgDist.AddEventListener(name, cb);
        }

        public void RemoveEventListener(string name, Action<ProtocolBase> cb)
        {
            _msgDist.RemoveEventListener(name, cb);
        }

        public void AddOnceListener(string name, Action<ProtocolBase> cb)
        {
            _msgDist.AddOnceListener(name, cb);
        }

        public void RemoveOnceListener(string name, Action<ProtocolBase> cb)
        {
            _msgDist.RemoveOnceListener(name, cb);
        }

        public void Close()
        {
            if (_tcpClient == null) return;

            lock (_tcpClient)
            {
                if (_status == Status.Connected)
                {
                    EndPoint endPoint = _tcpClient.Client.LocalEndPoint;
                    _tcpClient.Close();

                    _tcpClient = null;
                    _status = Status.Unconnected;
                    _isReconnecting = false;
                    TLogTool.Info($"【TcpClientNet】客户端({endPoint})与服务器断开网络连接");
                }
            }
        }

        #endregion

        #region Internal Func

        private void Reconnect()
        {
            try
            {
                if (_ipEndPoint != null)
                {
                    TLogTool.Info("【TcpClientNet】客户端尝试重新连接服务端");
                    _tcpClient = new TcpClient();
                    _tcpClient.BeginConnect(_ipEndPoint.Address, _ipEndPoint.Port, OnClientConnect, _tcpClient);
                }
            }
            catch (Exception ex)
            {
                TLogTool.Info("【TcpClientNet】客户端重新连接服务器时发生错误：" + ex);
            }
        }

        //发送心跳包
        private void SendHeartBeat()
        {
            ProtocolBytes protocol = new ProtocolBytes();
            protocol.AddString(Enum.GetName(typeof(RequestType), RequestType.Conn));
            protocol.AddString("HeartBeat");

            Send(protocol);
        }

        //连接上服务端时的回调
        private void OnClientConnect(IAsyncResult ar)
        {
            if (ar.AsyncState is TcpClient client && client.Connected)
            {
                client.EndConnect(ar);
                TLogTool.Info($"【TcpClientNet】客户端({client.Client.LocalEndPoint})连接上服务器");

                TcpClientStream?.BeginRead(_message.Buffer, _message.BuffCount, _message.BuffRemain, OnClientRead,
                    TcpClientStream);

                _status = Status.Connected;
                _isReconnecting = false;
                _activeSuccessFlag = true;
            }
        }

        //接收服务端消息的回调
        private void OnClientRead(IAsyncResult ar)
        {
            NetworkStream stream = ar.AsyncState as NetworkStream;
            if (stream == null || _tcpClient == null) return;
            int count = stream.EndRead(ar);
            try
            {
                if (count <= 0)
                {
                    _activeInterruptFlag = true;
                    Close();
                }
                else
                {
                    _message.ParseMessage(count, data =>
                    {
                        //将消息添加到消息队列
                        _msgDist.AddProtocol(_protocol.Decode(data));
                    });

                    TcpClientStream?.BeginRead(_message.Buffer, _message.BuffCount, _message.BuffRemain, OnClientRead,
                        TcpClientStream);
                }
            }
            catch (Exception e)
            {
                TLogTool.Error("【TcpClientNet】客户端接收信息时发生错误：" + e);
            }
        }

        #endregion
    }
}